/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file font.cpp
 * @author TooOld2Rock'nRoll
 * @see font.hpp
 */
/*---- Includes ----*/
#include <sys/stat.h>

#include "graphics/font.hpp"

#include "toolbox/debug.h"

#define STB_RECT_PACK_IMPLEMENTATION
#include "toolbox/stb/stb_rect_pack.h"
#define STB_TRUETYPE_IMPLEMENTATION
#include "toolbox/stb/stb_truetype.h"

// #define STB_IMAGE_WRITE_IMPLEMENTATION
// #include "toolbox/stb/stb_image_write.h"


/*---- Methods Implementation ----*/
/**
 * @brief Extended Constructor that loads a font file.
 * @remarks Instead of creating many Font objects to the same font file for different sizes, just aim at the middle and
 *    let OpenGl do its thing when scaling.
 *
 * @param file_path - path to the font file to be used as texture.
 * @param glyph_height - the size in pixels to render the font.
 *
 * @throws std::runtime_error - in case there is any problem loading the font file.
 */
Font::Font (const char *file_path, unsigned glyph_height) : Texture ()
{
    _debug (TRACE, "::%s (file_path=%s)", __FUNCTION__, file_path);

    this->load (file_path, glyph_height);
}//End Font Constructor

// Font::~Font ()
// {

// }//End Destructor

/**
 * @brief Tries to load a font file and set it as a texture.
 *
 * @param file_path - path to the font file to be used as texture.
 * @param glyph_height - the maximum vertical size in pixels a character will have.
 *
 * @throws std::runtime_error - in case there is any problem opening the font file or handling it as a font.
 *
 * @see stb_truetype.h
 */
void Font::load (const char *file_path, unsigned glyph_height)
{
    size_t i = 0, //we should probably never have such a large glyph set, but.....
           largest_codepoint = 0;

    FILE *f_stream = nullptr;
    struct stat sb;

    stbtt_fontinfo stb_font;
    stbtt_pack_context stb_ctx;
    stbtt_packedchar *stb_pack = nullptr;
    int glyph_id = -1,
        *glyph_list = nullptr;

    uint8_t *f_data = nullptr, //buffer to read the file content into
            *bm_data = nullptr, //the bitmap buffer that will be used to create the texture
            *aux = nullptr;
    size_t bytes_read = 0;

    int x0 = 0, x1 = 0,
        y0 = 0, y1 = 0,
        max_h = 0;
    int ascent = 0, descent = 0, line_gap = 0;
    float font_scale = 1.f;

    _debug (TRACE, "::%s (file_path=%s)", __FUNCTION__, file_path);

    if (this->loaded ())
        throw std::runtime_error (fmt::format ("{0}:{1} - This font was already loaded, can´t overwrite!", __LINE__, __FILE__, file_path));

    /* read the file from disk and save in a buffer */
    f_stream = fopen (file_path, "rb");
    if (!f_stream)
        throw std::runtime_error (fmt::format ("{0}:{1} - Failed to open file: {2}!", __LINE__, __FILE__, file_path));

    if (stat (file_path, &sb) == -1)
        throw std::runtime_error (fmt::format ("{0}:{1} - Failed to get file statistics: {2}!", __LINE__, __FILE__, file_path));
    else if (!sb.st_size)
        throw std::runtime_error (fmt::format ("{0}:{1} - File is empty: {2}!", __LINE__, __FILE__, file_path));

    f_data = new uint8_t[sb.st_size] ();
    memset (f_data, 0, sb.st_size);
    aux = &f_data[0];
    while ((bytes_read = fread (aux, 1, 1024, f_stream)) > 0)
        aux += bytes_read;

    fclose (f_stream);

    /* prep the buffer to become a font */
    if (stbtt_GetNumberOfFonts(f_data) == -1)
        throw std::runtime_error (fmt::format ("{0}:{1} - File contained no fonts in it: {2}!", __LINE__, __FILE__, file_path));

    //this will get general info about the font trying to be converted
    stbtt_InitFont (&stb_font, f_data, stbtt_GetFontOffsetForIndex (f_data, 0));
    font_scale = stbtt_ScaleForPixelHeight (&stb_font,  glyph_height);

    //save some info about the font
    this->st_font_statistics.u_glyph_count = stb_font.numGlyphs;

    stbtt_GetFontBoundingBox (&stb_font, &x0, &y0, &x1, &y1);
    // max_w = x1 - x0; //x0 is always positive or zero (are we sure???)
    max_h = y1 - y0; //y0 is always negative or zero (are we sure???)

    stbtt_GetFontVMetrics (&stb_font, &ascent, &descent, &line_gap);
    this->st_font_statistics.f_line_advance = (ascent - descent /*+ line_gap*/) * font_scale; //we are omitting line_gap, this may be bad
    //this is strange, according to documentation, baseline should be (-y0 * FS), but this puts the string outside the bounding box...
    //the current calculation gives better results, but we need to test with other fonts.
    this->st_font_statistics.f_baseline = (ascent + descent) * font_scale;

    //try to guess a bitmap size that will fit our scaled font
    //it should be oversize, but not ridiculously so!
    //now we need to check every codepoint and mark the existing ones
    glyph_list = new int[stb_font.numGlyphs] ();
    memset (glyph_list, 0x00, stb_font.numGlyphs * sizeof (int));
    for (i = 0xEFFFF; i--; )
    {

        glyph_id = stbtt_FindGlyphIndex (&stb_font, i);
        if (glyph_id == 0)
            continue; //glyph does not exist!
        else if (glyph_id >= stb_font.numGlyphs)
            continue; //should be an error????
        else if (i > largest_codepoint)
            largest_codepoint = i;//this should run only once!!!

        this->m_codepoint2glyph.emplace (i, glyph_id);
        if (glyph_list[glyph_id] != 0)
            continue; //glyph already found, this is a duplicate
        else if (stbtt_GetGlyphBox (&stb_font, glyph_id, &x0, &y0, &x1, &y1) == 0)
            continue; //codepoint has no printable char

        this->u_width += (x1 - x0);
        glyph_list[glyph_id] = i;
    }//end for (i = stb_font.numGlyphs; i--; )
    delete []glyph_list; //we don't need this anymore...
    this->u_width = (this->u_width * font_scale) / 4;
    this->u_height = (max_h * font_scale) * 3.5;
    bm_data = new uint8_t[this->u_width * this->u_height] ();

    //this will actually build the glyphs bitmap and how to find them
    stb_pack = new stbtt_packedchar[largest_codepoint] ();
    stbtt_PackBegin (&stb_ctx, bm_data, this->u_width, this->u_height, 0, 1, nullptr);
    // stbtt_PackSetOversampling (&stb_ctx, 2, 2); to use this, must double the bitmap sizes....
    stbtt_PackFontRange (&stb_ctx, f_data, 0, glyph_height, 0, largest_codepoint, stb_pack);
    stbtt_PackEnd (&stb_ctx);
    delete []f_data; //we don't need this any longer

    // stbi_write_bmp ("font.bmp", this->u_width, this->u_height, 1, bm_data); //uncomment for debug font!
    this->useMonocromeAsAlpha (true);
    this->Texture::load (bm_data, this->u_width, this->u_height, 1);
    delete []bm_data; //we don't need this any longer

    //now we save the limits of every glyph in the bitmap
    this->createTileMap (stb_font.numGlyphs);
    this->pa_glyph_stats = new Font::glyph_stat_s[stb_font.numGlyphs] ();
    for (i = largest_codepoint; i--; )
    {
        try
        { glyph_id = this->m_codepoint2glyph.at (i); }
        catch (const std::out_of_range &ex) //if codepoint has no glyph
        { continue; }

        this->mapTileByLimits (glyph_id, stb_pack[i].x0, stb_pack[i].y0, stb_pack[i].x1, stb_pack[i].y1);
        this->pa_glyph_stats[glyph_id].u_advance = stb_pack[i].xadvance;
        this->pa_glyph_stats[glyph_id].f_xoff = stb_pack[i].xoff; //xoff should be positive or zero
        this->pa_glyph_stats[glyph_id].f_yoff = this->st_font_statistics.f_baseline + stb_pack[i].yoff; //yoff should be negative
    }//end for (i = this->v_codepoint2glyph.size (); i--; )

    delete []stb_pack; //we don't need this any longer
}//End load (file)


/**
 * @brief Gets a reference to a specific tile's information.
 *
 * @param index - the tile index.
 *
 * @return A reference to the desired tile information.
 *
 * @throws std::runtime_error - if the texture has no tile map.
 * @throws std::out_of_range - if the tile index is greater than the max number of tiles supported.
 */
const Font::glyph_stat_s& Font::getGlyphStatistics (unsigned index) const
{
    _debug (TRACE, "::%s (index=%u)", __FUNCTION__, index);

    if (!this->pa_glyph_stats)
        throw std::runtime_error (fmt::format ("{0}:{1} - Font was not processed yet and have no glyph!", __LINE__, __FILE__));
    else if (index >= this->st_font_statistics.u_glyph_count)
        throw std::out_of_range (fmt::format ("{0}:{1} - Glyph out of range, {2} of {3}!", __LINE__, __FILE__, index, this->st_font_statistics.u_glyph_count - 1));

    return this->pa_glyph_stats[index];
}//end getGlyphStatistics ()
